package worker

import (
	"gitee.com/Cookie_XiaoD/crontab/common"
	"log"
	"time"
	"fmt"
)

//任务调度器
type Scheduler struct {
	//任务事件通道
	jobEventChan chan *common.JobEvent
	//任务调度计划
	jobSchedulePlanTable map[string]*common.JobSchedulePlan
	//正在执行的任务信息
	jobExecutingTable map[string]*common.JobExecuteInfo
	//任务执行结果
	jobExecuteResult chan *common.JobExecuteResult
}

var (
	G_scheduler *Scheduler
)

//初始化任务调度器
func InitScheduler() (err error) {
	G_scheduler = &Scheduler{
		jobEventChan:         make(chan *common.JobEvent, 1000),
		jobSchedulePlanTable: make(map[string]*common.JobSchedulePlan),
		jobExecutingTable:    make(map[string]*common.JobExecuteInfo),
		jobExecuteResult:     make(chan *common.JobExecuteResult, 1000),
	}

	go G_scheduler.schedulerLoop()
	return
}

//推送任务事件
func (scheduler *Scheduler) PushEventJob(jobEvent *common.JobEvent) {
	scheduler.jobEventChan <- jobEvent
}

//推送任务执行结果
func (scheduler *Scheduler) PushJobExeResult(result *common.JobExecuteResult) {
	scheduler.jobExecuteResult <- result
}

func (scheduler *Scheduler) TryStartJob(plan *common.JobSchedulePlan) {
	var (
		jobExInfo        *common.JobExecuteInfo
		jobExInfoExisted bool
	)
	if jobExInfo, jobExInfoExisted = scheduler.jobExecutingTable[plan.Job.Name]; jobExInfoExisted {
		log.Println("任务正在执行中", plan.Job.Name, "本次跳过,计划执行时间：", jobExInfo.PlanTime, " 实际执行时间：", jobExInfo.RealTime)
		return
	}

	jobExInfo = common.BuildJobExecuteInfo(plan)

	scheduler.jobExecutingTable[jobExInfo.Job.Name] = jobExInfo
	log.Println("执行任务：", plan.Job.Name)
	G_Executer.ExecuteJob(jobExInfo)

}

//尝试调度任务
//返回需要睡眠的时间
func (scheduler *Scheduler) trySchedule() (scheduleAfter time.Duration) {
	//迭代所有任务计划，若有过期任务则立即执行，且更新下一次的执行时间
	//根据计划中 最近一个要过期的任务，得出睡眠时间

	//如果当前无调度计划，则随意睡眠一秒
	if len(scheduler.jobSchedulePlanTable) == 0 {
		return 1 * time.Second
	}

	var (
		plan            *common.JobSchedulePlan
		now             time.Time
		nearestNextTime *time.Time
	)

	now = time.Now()

	for _, plan = range scheduler.jobSchedulePlanTable {
		//如果计划过期
		if plan.NextTime.Before(now) || plan.NextTime.Equal(now) {
			log.Println("任务过期，准备执行任务:" + plan.Job.Name)
			scheduler.TryStartJob(plan)

			//计算任务下一次调度时间
			plan.NextTime = plan.Expr.Next(now)
		}

		if nearestNextTime == nil || plan.NextTime.Before(*nearestNextTime) {
			nearestNextTime = &plan.NextTime
		}
	}

	//下一笔最近要过期的任务与现在时间的差就是我们要睡眠的时间
	//这样可以精确控制
	scheduleAfter = nearestNextTime.Sub(now)

	return
}

func (scheduler *Scheduler) schedulerLoop() {
	var (
		jobEvent      *common.JobEvent
		scheduleAfter time.Duration
		scheduleTimer *time.Timer //调度计时器
		jobResult     *common.JobExecuteResult
	)

	//存在任务事件或定时器到指定时间后，都需要尝试执行任务
	scheduleAfter = scheduler.trySchedule()
	scheduleTimer = time.NewTimer(scheduleAfter)

	for {
		select {
		case jobEvent = <-scheduler.jobEventChan:
			//修改内存中的任务列表
			scheduler.handleJobEvent(jobEvent)
		case <-scheduleTimer.C:
		case jobResult = <-scheduler.jobExecuteResult: //任务执行完毕后推送的任务结果
			scheduler.handleJobResult(jobResult)
		}

		scheduleAfter = scheduler.trySchedule()
		scheduleTimer.Reset(scheduleAfter)

	}

}

//处理任务事件
func (scheduler *Scheduler) handleJobEvent(jobEvent *common.JobEvent) {

	var (
		jobSchedulePlan *common.JobSchedulePlan
		jobPlanExisted  bool
		jobExecuting bool
		jobExecuteInfo *common.JobExecuteInfo
		err             error
	)

	//处理jobevent
	switch jobEvent.EventType {
	case common.JOB_EVENT_SAVE: //保存、修改
		if jobSchedulePlan, err = common.BuildJobSchedulePlan(jobEvent.Job); err != nil {
			return
		}
		scheduler.jobSchedulePlanTable[jobEvent.Job.Name] = jobSchedulePlan
	case common.JOB_EVENT_DELETE: //删除
		if jobSchedulePlan, jobPlanExisted = scheduler.jobSchedulePlanTable[jobEvent.Job.Name]; !jobPlanExisted {
			return
		}
		delete(scheduler.jobSchedulePlanTable, jobEvent.Job.Name)
	case common.JOB_EVENT_KILL://任务强杀
		if jobExecuteInfo, jobExecuting = scheduler.jobExecutingTable[jobEvent.Job.Name]; !jobExecuting {
			return
		}
		fmt.Println(jobExecuteInfo.Job.Name+" 任务正在执行，进行强杀")
		jobExecuteInfo.CancleFunc()

	}
}

func (scheduler *Scheduler) handleJobResult(result *common.JobExecuteResult) {
	delete(scheduler.jobExecutingTable, result.JobExecuteInfo.Job.Name)
	log.Printf("### %v 执行完毕 开始时间：%v 结束时间:%v 运行结果:%v 错误信息:%v", result.JobExecuteInfo.Job.Name,
		result.StartTime.Format("2006-01-02 15:04:05.000"),
		result.EndTime.Format("2006-01-02 15:04:05.000"),
		string(result.Output),result.Err)
}
